package com.mentorgen.tools.profile.runtime.jar;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.CheckClassAdapter;

import com.mentorgen.tools.profile.Controller;
import com.mentorgen.tools.profile.instrument.PerfClassAdapter;
import com.mentorgen.tools.profile.runtime.EntityDicHelper;
import com.mentorgen.tools.profile.runtime.EntityDictionary;
//import com.mentorgen.tools.profile.runtime.MethodDictionary;

public class TempBundleManager {
	
	public static final String INST_BUNDLE_DIRECTORY = "felix-inst";
	public static final String CLASS_EXTENSION = ".class";
	public static final String JAR_EXTENSION = ".jar";
	
	public static void createInstJarDir(String bsn){
		try{
			if (!new File(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") + bsn).exists()){
				boolean success = (new File(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") + bsn)).mkdirs();
				if (!success)
					throw new Exception("Failed to create the INST directory");
			}
			
		}catch(Exception e){
			if (Controller._debug)
				System.out.println(e.getMessage());
		}
	}
	
	private static void writeInstObjClass(String bsn, String className, byte[] content) throws IOException {
		
		int subpath = className.lastIndexOf(System.getProperty("file.separator"));
		if (subpath >= 0)
			(new File(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") + 
					bsn + System.getProperty("file.separator") + 
					className.substring(0, className.lastIndexOf(System.getProperty("file.separator"))))).mkdirs();
		else
			(new File(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") + 
					bsn)).mkdirs();
		
		if (className.endsWith(System.getProperty("file.separator")))
			return;
		
		OutputStream out = new FileOutputStream(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") + 
				bsn + System.getProperty("file.separator") + new File(className));
		
		out.write(content);
		out.flush();
		out.close();
	}
	
	public static void copyInstJarResource(String bsn, String lid, 
			URI jarURI, ClassLoader loader) throws IOException, ClassNotFoundException{

		File jarFile;
		if (jarURI.isOpaque())
				jarFile = new File(jarURI.normalize().getSchemeSpecificPart());
		else
			jarFile = new File(jarURI);
		
		if (jarFile.getAbsolutePath().contains(INST_BUNDLE_DIRECTORY))
//			|| 
//			new File(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") + bsn + TempBundleManager.JAR_EXTENSION).exists())
			return;
		
		System.out.println("instrumenting: " + bsn);
		
		JarFile jar = new JarFile(jarFile);
		byte[] buffer;
		
		for (Enumeration<?> entries = jar.entries(); entries.hasMoreElements(); ){
			
			JarEntry entry = (JarEntry) entries.nextElement();
			
			InputStream entryStream = jar.getInputStream(entry);
			byte[] tmp = new byte[1024];
			buffer = new byte[(int) entry.getSize()];
			
			int bytesRead = 0;
			int totalRead = 0;
			while ((bytesRead = entryStream.read(tmp)) != -1){
				System.arraycopy(tmp, 0, buffer, totalRead, bytesRead);
				totalRead += bytesRead;
			}
			
			if (!entry.getName().endsWith(CLASS_EXTENSION)){
				
				writeInstObjClass(bsn, entry.getName(), buffer);
				
			}else if (entry.getName().endsWith(TempBundleManager.CLASS_EXTENSION)){
				
//				System.out.println(entry.getName());
				
				Controller._instrumentCount++;
				
				EntityDictionary.getloaderClassDic().put(entry.getName(), 
						Integer.toString(loader.hashCode()));

				ClassReader reader = new ClassReader(buffer);
				ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
				ClassAdapter adapter = new PerfClassAdapter(writer, lid, bsn, entry.getName());
				reader.accept(adapter, ClassReader.EXPAND_FRAMES);
				
				//writes the content of the class to the temp folder managed by TempBundleManager
				byte[] classContent = writer.toByteArray();
				
				// verifying the content of the class before writing it to a class file
				// putting an iff statement to filter out only the content for the reader bundle
				String tmpEntryName = entry.getName();
				int    tmpLastIndex = entry.getName().lastIndexOf(System.getProperty("file.separator"));
				if (bsn.contains("com.notehive.osgi.rubis.hibernate-osgi-rubis-web-core") &&
						tmpEntryName.substring(tmpLastIndex).contains("HibernateRubisHttpServlet.class")){
					System.err.println(loader.getClass().getName() + " " + loader.hashCode() + " :: " + 
							entry.getName().substring(entry.getName().lastIndexOf(System.getProperty("file.separator"))));
					PrintWriter pw = new PrintWriter(new File("log/" + 
							entry.getName().substring(entry.getName().lastIndexOf(System.getProperty("file.separator"))) + 
							Long.toString(System.currentTimeMillis()) + ".txt"));
					CheckClassAdapter.verify(new ClassReader(classContent), loader, true, pw);
					pw.flush();
					pw.close();
				}
//				
//				String tmpEntryName = entry.getName();
//				int    tmpLastIndex = entry.getName().lastIndexOf(System.getProperty("file.separator"));
//				if (tmpEntryName.substring(tmpLastIndex).contains("User.class") ||
//						tmpEntryName.substring(tmpLastIndex).contains("HibernateException.class") ||
//						tmpEntryName.substring(tmpLastIndex).contains("SessionFactory.class"))
//					System.err.println(loader.getClass().getName() + ":" + loader.hashCode() + " ==> " + tmpEntryName.substring(tmpLastIndex));
				
				TempBundleManager.writeInstObjClass(bsn, entry.getName(), classContent);
			}
		}
		new JarHelper().jarDir(new File(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") +	bsn), 
				new File(TempBundleManager.INST_BUNDLE_DIRECTORY + System.getProperty("file.separator") + bsn + TempBundleManager.JAR_EXTENSION));
	}
	
	public static boolean deleteTempDir(){
		return TempBundleManager.recursiveDelete(new File(TempBundleManager.INST_BUNDLE_DIRECTORY));
	}
	
//	public Thread getShutDownHook(){
//		return new Thread(new DeleteTmpDirHook());
//	}
	
	private static boolean recursiveDelete(File fileOrDir)
	{
	    if(fileOrDir.isDirectory())
	    {
	        // recursively delete contents
	        for(File innerFile: fileOrDir.listFiles())
	        {
	            if(!TempBundleManager.recursiveDelete(innerFile))
	            {
	                return false;
	            }
	        }
	    }

	    return fileOrDir.delete();
	}
	
//	private class DeleteTmpDirHook implements Runnable {
//
//		@Override
//		public void run() {
//			TempBundleManager.deleteTempDir();
//		}
//	}
}
